/**
 *
 */
package com.angel.data.generator.repository;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.log4j.Logger;

import com.angel.common.helpers.ReflectionHelper;
import com.angel.data.generator.base.DataClassGenerator;
import com.angel.data.generator.comparators.DataClassGeneratorDependencesDescComparator;
import com.angel.data.generator.exceptions.DataGeneratorException;
import com.angel.data.generator.interfaces.DataGenerator;
import com.angel.data.generator.sorter.DataGeneratorSorter;

/**
 * @author William
 *
 */
public class DataGeneratorRepository {

	private static Logger LOGGER = Logger.getLogger(DataGeneratorRepository.class);
	private Map<DataClassGenerator, DataGenerator[]> dependenciesMap = new HashMap<DataClassGenerator, DataGenerator[]>();

	public DataGeneratorRepository(){
		super();
	}

	public DataGenerator[] getDependencies() {
		DataGenerator[] dataGenerators = new DataGenerator[this.dependenciesMap.size()];
		int index = 0;
		for(DataClassGenerator classGenerator: this.dependenciesMap.keySet()){
			dataGenerators[index] = classGenerator.getDataGenerator();
			index++;
		}
		return dataGenerators;
	}

	public Collection<DataClassGenerator> getInverseSortedDataClassGenerators() {
		SortedSet<DataClassGenerator> dataClassGeneratorSortedSet = new TreeSet<DataClassGenerator>(new DataClassGeneratorDependencesDescComparator());
		dataClassGeneratorSortedSet.addAll(this.dependenciesMap.keySet());
		this.logOrderSortedColelction(dataClassGeneratorSortedSet,"Inverse Orde (Desc):");
		return dataClassGeneratorSortedSet;
	}

	private void logOrderSortedColelction(Collection<DataClassGenerator> dataClassGeneratorSortedSet, String message) {
		int index = 1;
		LOGGER.info(message + " - Size: [" + dataClassGeneratorSortedSet.size() + "].");
		for(DataClassGenerator dtg:dataClassGeneratorSortedSet){
			LOGGER.debug("\t"+ index + " - " + dtg.getDataGeneratorClassSimpleName());
			index++;
		}
	}

	public List<DataClassGenerator> getSortedDataClassGenerators() {
		DataGeneratorSorter dataGeneratorSorter = new DataGeneratorSorter(this.dependenciesMap.keySet());
		List<DataClassGenerator> sortedDataClassGeneratorList = dataGeneratorSorter.sortDataClassGenerators();
		Collections.reverse(sortedDataClassGeneratorList);
		this.logOrderSortedColelction(sortedDataClassGeneratorList," - Sorted Order (Asc):");
		return sortedDataClassGeneratorList;
	}

	public DataGenerator[] getSortedDependencies() {
		List<DataClassGenerator> dataClassGeneratorSortedSet = this.getSortedDataClassGenerators();
		DataGenerator[] dataGenerators = new DataGenerator[dataClassGeneratorSortedSet.size()];
		int index = 0;
		for(DataClassGenerator classGenerator: dataClassGeneratorSortedSet){
			dataGenerators[index] = classGenerator.getDataGenerator();
			index++;
		}
		return dataGenerators;
	}

	public Collection<DataClassGenerator> getDataClassGenerator(){
		return dependenciesMap.keySet();
	}

	public DataGenerator[] getDependenciesFor(Class<? extends DataGenerator> dataGeneratorClass){
		if(this.containsDataGeneratorClassAsGenerator(dataGeneratorClass)){
			return this.dependenciesMap.get(this.getDataClassGeneratorFor(dataGeneratorClass));
		} else {
			throw new DataGeneratorException("Data class generator dependence [" + dataGeneratorClass.getCanonicalName() + "] was not found.");
		}
	}

	/** Test if repository contains dependencies for a data generator instance.
	 *
	 * @param dataGenerator to tests.
	 * @return true if repository contains dependencies for a data generator instance. Otherwise it returns false.
	 */
	public boolean containsDependenciesFor(DataGenerator dataGenerator){
		boolean contains = false;
		for(DataGenerator currentDataGenerator: this.getDependencies()){
			if(currentDataGenerator.getClass().equals(dataGenerator.getClass())){
				contains = true;
				break;
			}
		}
		return contains;
	}

	/** Put at repository a data generator. It will be used for creating domain object instances.
	 *
	 * @param dataGeneratorClass to add at repository.
	 */
	public void putDependenciesFor(Class<? extends DataGenerator> dataGeneratorClass){
		this.putDependenciesFor(this.createDataGeneratorFor(dataGeneratorClass));
	}

	public void putDependenciesFor(DataGenerator dataGenerator){
		LOGGER.debug("Adding a data class generator dependence [" + dataGenerator.getClass().getCanonicalName() + "].");
		DataGenerator[] dependencies = this.buildDependenciesArrayFor(dataGenerator);
		DataClassGenerator dataClassGenerator = new DataClassGenerator(dataGenerator);
		int index = 0;
		if(dataClassGenerator.hasDependencies()){
			for(Class<?> dependencyClass: dataGenerator.getDependecesGenerators()){
				DataGenerator dependency = this.createDataGeneratorFor(dependencyClass);
				dependencies[index] = dependency;
				index++;
			}
		}
		this.dependenciesMap.put(dataClassGenerator, dependencies);
	}

	/**
	 * Build a data generator array with data generator dependencies length. If data generator has not dependencies, array length is zero.
	 *
	 * @param dataGenerator to find its dependencies.
	 * @return a data generator array with data generator dependencies length, or with zero length.
	 */
	protected DataGenerator[] buildDependenciesArrayFor(DataGenerator dataGenerator){
		return new DataGenerator[dataGenerator.getDependecesGenerators() != null? dataGenerator.getDependecesGenerators().length : 0];
	}

	protected DataGenerator createDataGeneratorFor(Class<?> dataGeneratorClass){
		LOGGER.debug("Creating a data class generator instance for [" + dataGeneratorClass.getCanonicalName() + "].");
		DataGenerator dataGenerator = (DataGenerator) ReflectionHelper.createObject(dataGeneratorClass);
		if(dataGenerator == null){
			LOGGER.debug("Data class generator instance for [" + dataGeneratorClass.getCanonicalName() + "] is null. Data class generator must implements a default constructor.");
			throw new DataGeneratorException("Could not create an instance for data generator class [" + dataGeneratorClass + "].");
		}
		return dataGenerator;
	}

	public boolean containsDataGeneratorAsDependece(Class<? extends DataGenerator> dependencyClass){
		Collection<DataGenerator[]> values = dependenciesMap.values();
		if( values != null ){
			for(DataGenerator[] dataGeneratorArray: values){
				for(DataGenerator dataGenerator: dataGeneratorArray){
					if(dataGenerator.getClass().equals(dependencyClass)){
						LOGGER.debug("Data generator instance is contained as dependence.");
						return true;
					}
				}
			}
		}
		LOGGER.info("Data generator instance is NOT contained as dependence.");
		return false;
	}

	public boolean containsDataGeneratorClassAsGenerator(Class<? extends DataGenerator> dependencyClass){
		boolean contains = false;
		for(DataClassGenerator dataClassGenerator: dependenciesMap.keySet()){
			if(dataClassGenerator.isForDataGenerator(dependencyClass)){
				contains = true;
				break;
			}
		}
		return contains;
	}

	public boolean removeDataGenerator(Class<? extends DataGenerator> dataGeneratorClass) {
		LOGGER.debug("Removing data class generator from repository.");
		boolean wasRemoved = false;
		if(this.containsDataGeneratorClassAsGenerator(dataGeneratorClass)){
			DataClassGenerator dataClassGeneratorToDelete = null;
			for(DataClassGenerator dataClassGenerator: dependenciesMap.keySet()){
				if(dataClassGenerator.isForDataGenerator(dataGeneratorClass)){
					dataClassGeneratorToDelete = dataClassGenerator;
					wasRemoved = true;
				}
			}
			return dependenciesMap.remove(dataClassGeneratorToDelete) != null;
		}
		LOGGER.debug("Data class generator was removed? ["+ wasRemoved +"]. Now repository contains [" + this.dependenciesMap.size() + "] data class generators.");
		return wasRemoved;
	}

	public DataGenerator getDataGenerator(Class<? extends DataGenerator> dataGeneratorClass) {
		if(this.containsDataGeneratorClassAsGenerator(dataGeneratorClass)){
			for(DataClassGenerator dataClassGenerator: dependenciesMap.keySet()){
				if(dataClassGenerator.isForDataGenerator(dataGeneratorClass)){
					return dataClassGenerator.getDataGenerator();
				}
			}
		}
		return null;
	}

	public DataClassGenerator getDataClassGeneratorFor(Class<? extends DataGenerator> dataGeneratorClass) {
		if(this.containsDataGeneratorClassAsGenerator(dataGeneratorClass)){
			for(DataClassGenerator dataClassGenerator: dependenciesMap.keySet()){
				if(dataClassGenerator.isForDataGenerator(dataGeneratorClass)){
					return dataClassGenerator;
				}
			}
		}
		return null;
	}

	public void clear() {
		LOGGER.debug("Clearing repository. It will clear [" + this.dependenciesMap.size() + "] data class generators.");
		dependenciesMap.clear();
		dependenciesMap = null;
		dependenciesMap = new HashMap<DataClassGenerator, DataGenerator[]>();
		LOGGER.debug("Repository was cleared. Now it contains [" + this.dependenciesMap.size() + "] data class generators.");
	}

	public Long size(){
		return this.dependenciesMap != null ? this.dependenciesMap.size() : new Long(0);
	}

}
